package gl.drawer

import gl.window
import org.lwjgl.glfw.GLFW
import org.lwjgl.opengl.GL44C.*

class DrawArraysInstancedAndIndirect: Drawer() {

    private var program = 0
    private var buffer = 0
    private var bufferIndirect = 0
    private var vertex = 0
    private var baseInstance: Int = 0
    private var isIndirect = false

    private val vertices = floatArrayOf(
          -1f, -1f,
        -0.5f,  1f,
           0f, -1f,
        0.1f, 0.1f, 0.1f,
        0.2f, 0.2f, 0.2f,
        0.3f, 0.3f, 0.3f,
        0.4f, 0.4f, 0.4f,
        0.5f, 0.5f, 0.5f,
        0.6f, 0.6f, 0.6f,
        0.7f, 0.7f, 0.7f,
        0.8f, 0.8f, 0.8f,
        0.9f, 0.9f, 0.9f,
    )

    private val renderData = intArrayOf(
        3, // count
        2, // primCount
        0, // first
        0, // baseInstance
    )

    override fun initialize() {
        val vs = glCreateShader(GL_VERTEX_SHADER)
        glShaderSource(vs, daiVertexShader)
        glCompileShader(vs)
        val fs = glCreateShader(GL_FRAGMENT_SHADER)
        glShaderSource(fs, daiFragmentShader)
        glCompileShader(fs)
        program = glCreateProgram()
        glAttachShader(program, vs)
        glAttachShader(program, fs)
        glLinkProgram(program)
        glDetachShader(program, vs)
        glDetachShader(program, fs)
        glDeleteShader(vs)
        glDeleteShader(fs)

        buffer = glGenBuffers()
        glBindBuffer(GL_ARRAY_BUFFER, buffer)
        glBufferStorage(GL_ARRAY_BUFFER, vertices.size * 4L, 0)
        glBufferSubData(GL_ARRAY_BUFFER, 0L, vertices)
        bufferIndirect = glGenBuffers()
        glBindBuffer(GL_DRAW_INDIRECT_BUFFER, bufferIndirect)
        glBufferStorage(GL_DRAW_INDIRECT_BUFFER, 16L, 0)
        glBufferSubData(GL_DRAW_INDIRECT_BUFFER, 0L, renderData)

        vertex = glGenVertexArrays()
        glBindVertexArray(vertex)
        glEnableVertexAttribArray(0)
        glVertexAttribPointer(0, 2, GL_FLOAT, false, 0, 0L)
        glEnableVertexAttribArray(1)
        glVertexAttribPointer(1, 3, GL_FLOAT, false, 0, 24L)
    }

    override fun resume() {
        super.resume()
        glUseProgram(program)
        glBindBuffer(GL_ARRAY_BUFFER, buffer)
        glBindBuffer(GL_DRAW_INDIRECT_BUFFER, bufferIndirect)
        glBindVertexArray(vertex)
        glEnableVertexAttribArray(0)
        glEnableVertexAttribArray(1)
        glVertexAttribDivisor(1, 1)
        window.title = "$name--$baseInstance"
    }

    override fun update(): Boolean {
        super.update()
        if (isIndirect) {
            glDrawArraysIndirect(GL_TRIANGLES, 0L)
        } else {
            glDrawArraysInstancedBaseInstance(GL_TRIANGLES, 0, 3, 2, baseInstance)
        }
        return false
    }

    override fun stop() {
        super.stop()
        glVertexAttribDivisor(1, 0)
        glDisableVertexAttribArray(0)
        glDisableVertexAttribArray(1)
        glBindVertexArray(0)
        glBindBuffer(GL_ARRAY_BUFFER, 0)
        glBindBuffer(GL_DRAW_INDIRECT_BUFFER, 0)
        glUseProgram(0)
    }

    override fun terminal() {
        glDeleteVertexArrays(vertex)
        glDeleteBuffers(intArrayOf(buffer, bufferIndirect))
        glDeleteProgram(program)
        baseInstance = 0
    }

    override fun handleKey(keyCode: Int, action: Int) {
        super.handleKey(keyCode, action)
        when (keyCode) {
            GLFW.GLFW_KEY_KP_0 -> baseInstance = 0
            GLFW.GLFW_KEY_KP_1 -> baseInstance = 1
            GLFW.GLFW_KEY_KP_2 -> baseInstance = 2
            GLFW.GLFW_KEY_KP_3 -> baseInstance = 3
            GLFW.GLFW_KEY_KP_4 -> baseInstance = 4
            GLFW.GLFW_KEY_KP_5 -> baseInstance = 5
            GLFW.GLFW_KEY_KP_6 -> baseInstance = 6
            GLFW.GLFW_KEY_KP_7 -> baseInstance = 7
            GLFW.GLFW_KEY_KP_8 -> baseInstance = 8
            GLFW.GLFW_KEY_KP_9 -> baseInstance = 9
            GLFW.GLFW_KEY_KP_ADD -> isIndirect = true
            GLFW.GLFW_KEY_KP_SUBTRACT -> isIndirect = false
        }
        window.title = when {
            isIndirect -> "$name -- indirect"
            baseInstance == 0 -> name
            else -> "$name -- $baseInstance"
        }
    }
}

val daiVertexShader = """
#version 400 core

layout (location=0) in vec4 vPosition;
layout (location=1) in vec3 vColor;

out vec4 color;

void main() {
    if (gl_InstanceID == 0) {
        gl_Position = vPosition;
        color = vec4(vColor, 1);
    } else {
        gl_Position = vec4(vPosition.x + 1, vPosition.yzw);
        color = vec4(1 - vColor, 1);
    }
}
""".trimIndent()

val daiFragmentShader = """
#version 400 core

in vec4 color;
out vec4 fColor;

void main() {
    fColor = color;
}
""".trimIndent()